/*Scala code license:

 Copyright 2009/10 Osman Pub

 Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 this file except in compliance with the License.

 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software distributed
 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.

 Orginal java code credits:
/**
 * JOGLGearsDemo.java <BR>
 * author: Brian Paul (converted to Java by Ron Cemer and Sven Goethel) <P>
 *
 * This version is equal to Brian Paul's version 1.2 1999/10/21
 */
*/

package demos

import com.sun.opengl.util._
import java.awt._
import java.awt.event._
import javax.media.opengl._
import scala.actors.Actor.actor

object JOGLGearsDemo extends MouseAdapter with GLEventListener {
  def main(args: Array[String]) {
    val frame = new Frame("Gear Demo")
    val canvas = new GLCanvas

    canvas addGLEventListener this
    frame add canvas; frame setSize(300, 300)

    val animator = new Animator(canvas)

    frame addWindowListener new WindowAdapter {
        override def windowClosing(e: WindowEvent) =
          // Run this on another thread than the AWT event queue to make sure
          // the call to Animator.stop() completes before exiting
          actor { animator stop; System exit 0 }
      }
    frame setVisible true; animator start
  }

  private val pi = scala.math.Pi.asInstanceOf[Float]
  private var (angle, view_rotx, view_roty) = (0f, 20f, 30f)
  private val view_rotz = 0f
  private var gear1, gear2, gear3, prevMouseX, prevMouseY: Int = _
  private var mouseRButtonDown = false

  def display(drawable: GLAutoDrawable) {
    // Turn the gears' teeth
    angle += 2f

    // Get the GL corresponding to the drawable we are animating
    val gl = drawable getGL

    // Special handling for the case where the GLJPanel is translucent
    // and wants to be composited with other Java 2D content
    if (drawable.isInstanceOf[GLJPanel])
      drawable match {
        case p: GLJPanel =>
          if(!p.isOpaque && p.shouldPreserveColorBufferIfTranslucent)
            gl glClear GL.GL_DEPTH_BUFFER_BIT
        case _ =>
      }
    else gl glClear GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT

    // Rotate the entire assembly of gears based on how the user dragged the
    // mouse around
    gl glPushMatrix;
    gl glRotatef(view_rotx, 1, 0, 0)
    gl glRotatef(view_roty, 0, 1, 0)
    gl glRotatef(view_rotz, 0, 0, 1)

    // Place the first gear and call its display list
    gl glPushMatrix;
    gl glTranslatef(-3, -2, 0)
    gl glRotatef (angle, 0, 0, 1)
    gl glCallList gear1
    gl glPopMatrix

    // Place the second gear and call its display list
    gl glPushMatrix;
    gl glTranslatef(3.1f, -2, 0)
    gl glRotatef(-2 * angle - 9, 0, 0, 1)
    gl glCallList gear2
    gl glPopMatrix

    // Place the third gear and call its display list
    gl glPushMatrix;
    gl glTranslatef(-3.1f, 4.2f, 0)
    gl glRotatef(-2 * angle - 25, 0, 0, 1)
    gl glCallList gear3
    gl glPopMatrix

    // Remember that every push needs a pop this one is paired with rotating
    // the entire gear assembly
    gl glPopMatrix
  }

  def displayChanged(drawable: GLAutoDrawable , modeChanged: Boolean,
                     deviceChanged: Boolean) {}

  def init(drawable: GLAutoDrawable) {
    // Use debug pipeline drawable.setGL(new DebugGL(drawable.getGL()))

    val gl = drawable getGL;
    gl setSwapInterval 1

    val pos = Array(5, 5, 10, 0f)
    val (red, green, blue) = (Array(0.8f, 0.1f, 0, 1),
                              Array(0, 0.8f, 0.2f, 1),
                              Array(0.2f, 0.2f, 1, 1))

    gl glLightfv(GL GL_LIGHT0, GL GL_POSITION, pos, 0)
    gl glEnable GL.GL_CULL_FACE; gl glEnable GL.GL_LIGHTING
    gl glEnable GL.GL_LIGHT0; gl glEnable GL.GL_DEPTH_TEST

    /* make the gears */
    gear1 = gl glGenLists 1
    gl glNewList(gear1, GL GL_COMPILE)
    gl glMaterialfv(GL GL_FRONT, GL GL_AMBIENT_AND_DIFFUSE, red, 0)
    gear(gl, 1, 4, 1, 20, 0.7f)
    gl glEndList

    gear2 = gl glGenLists 1
    gl glNewList(gear2, GL GL_COMPILE)
    gl glMaterialfv(GL GL_FRONT, GL GL_AMBIENT_AND_DIFFUSE, green, 0)
    gear(gl, 0.5f, 2, 2, 10, 0.7f)
    gl glEndList

    gear3 = gl glGenLists 1
    gl glNewList(gear3, GL GL_COMPILE)
    gl glMaterialfv(GL GL_FRONT, GL GL_AMBIENT_AND_DIFFUSE, blue, 0)
    gear(gl, 1.3f, 2, 0.5f, 10, 0.7f)
    gl glEndList

    gl glEnable GL.GL_NORMALIZE

    drawable addMouseListener this; drawable addMouseMotionListener this
  }

  def reshape(drawable: GLAutoDrawable, x: Int, y: Int, width: Int, height: Int) {
    require(width != 0)

    val gl = drawable getGL
    val h: Float = height / width

    gl glMatrixMode GL.GL_PROJECTION

    gl glLoadIdentity; gl glFrustum(-1, 1, -h, h, 5, 60)
    gl glMatrixMode GL.GL_MODELVIEW; gl.glLoadIdentity
    gl glTranslatef(0, 0, -40)
  }

  // Methods required for the implementation of MouseMotionListener
  override def mouseDragged(e: MouseEvent) {
    val (x: Int, y: Int) = (e getX, e getY)
    val size = e.getComponent getSize
    
    val thetaY: Float = 360 * (x - prevMouseX) / size.width
    val thetaX: Float = 360 * (prevMouseY - y) / size.height

    prevMouseX = x; prevMouseY = y
    view_rotx += thetaX; view_roty += thetaY
  }

  override def mousePressed(e: MouseEvent) {
    prevMouseX = e getX; prevMouseY = e getY;
    if((e.getModifiers & InputEvent.BUTTON3_MASK) != 0) mouseRButtonDown = true
  }
  override def mouseReleased(e: MouseEvent) =
    if((e.getModifiers & InputEvent.BUTTON3_MASK) != 0) mouseRButtonDown = false

  private def gear(gl: GL, inner_radius: Float, outer_radius: Float,
                   width: Float, teeth: Int, tooth_depth: Float) {
    val da = pi / teeth / 2
    val (r0, r1, r2) = (inner_radius,
                        outer_radius - tooth_depth / 2,
                        outer_radius + tooth_depth / 2)
    val w = width / 2
    var i = 0

    def drawFace(mode: Int, draw: (Array[Float], Array[Float]) => Unit) {
      gl glBegin mode
      for(i <- 0 to teeth) {
        val angle = i * 2f * pi / teeth
        val sin = Array(fsin(angle), fsin(angle + da), fsin(angle + 2f * da),
                        fsin(angle + 3f * da))
        val cos = Array(fcos(angle), fcos(angle + da), fcos(angle + 2f * da),
                        fcos(angle + 3f * da))
        draw(sin, cos)
      }
      gl glEnd
    }
    def fcos(angle: Float) = scala.math.cos(angle).asInstanceOf[Float]
    def fsin(angle: Float) = scala.math.sin(angle).asInstanceOf[Float]

    gl glShadeModel GL.GL_FLAT; gl glNormal3f(0, 0, 1)

    drawFace(GL GL_QUAD_STRIP, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw front face */
        gl glVertex3f(r0 * c(0), r0 * s(0), w)
        gl glVertex3f(r1 * c(0), r1 * s(0), w)
        if(i < teeth) {
          gl glVertex3f(r0 * c(0), r0 * s(0), w)
          gl glVertex3f(r1 * c(3), r1 * s(3), w)
        }
      }
    )

    drawFace(GL GL_QUADS, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw front sides of teeth */
        gl glVertex3f(r1 * c(0), r1 * s(0), w)
        gl glVertex3f(r2 * c(1), r2 * s(1), w)
        gl glVertex3f(r2 * c(2), r2 * s(2), w)
        gl glVertex3f(r1 * c(3), r1 * s(3), w)
      }
    )

    drawFace(GL GL_QUAD_STRIP, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw back face */
        gl glVertex3f(r1 * c(0), r1 * s(0), -w)
        gl glVertex3f(r0 * c(0), r0 * s(0), -w)
        gl glVertex3f(r1 * c(3), r1 * s(3), -w)
        gl glVertex3f(r0 * c(0), r0 * s(0), -w)
      }
    )

    drawFace(GL GL_QUADS, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw back sides of teeth */
        gl glVertex3f(r1 * c(3), r1 * s(3), -w)
        gl glVertex3f(r2 * c(2), r2 * s(2), -w)
        gl glVertex3f(r2 * c(1), r2 * s(1), -w)
        gl glVertex3f(r1 * c(0), r1 * s(0), -w)
      }
    )

    drawFace(GL GL_QUAD_STRIP, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw outward faces of teeth */
        gl glVertex3f(r1 * c(0), r1 * s(0), w)
        gl glVertex3f(r1 * c(0), r1 * s(0), -w)

        val (u, v) = (r2 * c(1) - r1 * c(0), r2 * s(1) - r1 * s(0))
        val (u1, v1) = (r1 * c(3) - r2 * c(2), r1 * s(3) - r2 * s(2))
        val len = scala.math.sqrt(u * u + v * v).asInstanceOf[Float]

        gl glNormal3f(v / len, -u / len, 0)
        gl glVertex3f(r2 * c(1), r2 * s(1), w)
        gl glVertex3f(r2 * c(1), r2 * s(1), -w)
        gl glNormal3f(c(0), s(0), 0)
        gl glVertex3f(r2 * c(2), r2 * s(2), w)
        gl glVertex3f(r2 * c(2), s(2), -w)

        gl glNormal3f(v1, -u1, 0)
        gl glVertex3f(r1 * c(3), r1 * s(3), w)
        gl glVertex3f(r1 * c(3), r1 * s(3), -w)
        gl glNormal3f(c(0), s(0), 0)
      }
    )
    
    gl glVertex3f(r1, 0, w)
    gl glVertex3f(r1, 0, -w)
    gl glEnd

    gl glShadeModel GL.GL_SMOOTH

    drawFace(GL GL_QUAD_STRIP, (s: Array[Float], c: Array[Float]) =>
      {
        /* draw inside radius cylinder */
        gl glNormal3f(-c(0), -s(0), 0)
        gl glVertex3f(r0 * c(0), r0 * s(0), -w)
        gl glVertex3f(r0 * c(0), r0 * s(0), w)
      }
    )
  }
}
